Android — Activity 异常情况下的生命周期

前言

看了任玉刚老师的 living 之后,只能说大牛就是大牛。怀着敬畏的态度研读他的著作 — 《Android 开发艺术探索》,毫不夸张的说这本书绝对算是 Android 进阶必读书了。本菜鸡看了第一章就收获颇丰,以下就是一些读书笔记,例子都是枪书上的,感谢任老师。

Activity 正常情况下的生命周期估计大家随口都能说出,那么在异常情况下的生命周期你知道吗?首先,哪些是异常情况呢?emmmm,有以下常见的两种情况:

  1. 资源相关的系统配置发生改变导致 Activity 被杀死并重新创建。
  2. 资源内存不足导致低优先级的 Activity 被杀死

对于情况一,乍一听可能有点懵,那就举一个实例 — 横竖屏的切换。我们知道,在这种情况下,Activity 是销毁后再重新创建的,当然,我们也可以阻止系统重新创建Activity 。但是,他不会是简单的销毁重建,为什么这么说呢?让我们页面上的视频时,看到一半我们切换到全屏,这时候它会保存我们的观看进度,那这是这么实现的呢?对于情况二,这个还真的不好模拟,那就只能理论分析咯。

情况一:

当系统配置发生改变之后,Activity 会被销毁,其 onPause、onStop、onDestroy 均会被调用,同时由于Activity 是在异常情况下终止的,系统会调用 onSaveInstanceState 来保存当前Activity 的状态。这个方法调用是在 onStop 之前,它和 onPause 没有既定的时序关系,即可能在 onPause 之前,也可能在它之后。需要强调的是,这个方法只会在Activity 被异常终止的情况下才会调用,正常情况下,系统不会调用这个方法。当Activity 被重新创建的时候,系统会调用 onRestoreInstanceState 方法,并且把Activity 销毁时 onSaveInstanceState 方法所保存的 Bundle 对象同时作为参数传递给 onRestoreInstanceState 和 onCreat 方法。因此,我们可以通过 onRestoreInstanceState 和 onCreat 方法来判断 Activity 是否被重建了,如果被重建了,那么我们可以取出之前保存的数据并恢复。从时序上来说,onRestoreInstanceState 的调用时机在 onStart 之后。

同时,我们要知道,在 onSaveInstanceState 和 onRestoreInstanceState 方法中,系统自动为我们做了一定的恢复工作。当 Activity 在异常情况下需要重新创建时,系统会默认为我们保存当前 Activity 的视图结构,并且在 Activity 重启后为我们恢复这些数据,比如文本框用户输入的数据、ListView 滚动的位置等等。

验证:

我们可以模拟一个实例,点击按钮进行横竖屏的切换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package top.omooo.booknoteexample;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button mButton;
private Context mContext = MainActivity.this;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = findViewById(R.id.btn_flipScreen);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
});
Log.i(TAG, "onCreate");
if (savedInstanceState != null) {
String test = savedInstanceState.getString("Test");
Log.i(TAG, "[onCreate] onRestoreInstanceState" + " 销毁后获得数据:" + test);
}
}
@Override
protected void onStart() {
super.onStart();
Log.i(TAG, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG, "onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.i(TAG, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.i(TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy");
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("Test", "test");
Log.i(TAG, "onSaveInstanceState");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
String test = savedInstanceState.getString("Test");
Log.i(TAG, "[onRestoreInstanceState] onRestoreInstanceState" + " 销毁后获得数据:" + test);
}
}

以下是点击按钮之后打印的日志:

显然,onSaveInstanceState 是在 onStop 之前调用,onRestoreInstanceState 是在 onStart 之后调用。而且,可以看到,在重新创建 Activity 的时候,onCreat 方法里面的 Bundle 参数已经有值了,该参数也就是 onSaveInstanceState 传递过来的 Bundle 对象。

情况二:

先简单描述一下 Activity 的优先级情况,按照优先级从高到低,可以分为一下三种:

  1. 前台 Activity ——-正在和用户交互的 Activity ,优先级最高。
  2. 可见但非前台 Activity ——-比如 Activity 中弹出了一个对话框,导致 Activity 可见但是位于后台无法和用户交互。
  3. 后台 Activity ——已经被暂停的 Activity ,比如执行了 onStop,优先级最低。

当系统内存不足时,系统会按照上述优先级去销毁目标 Activity 所在的进程 ,并在后续通过 onSaveInstanceState 和 onRestoreInstanceState 来存储和恢复数据。如果一个进程中没有四大组件在执行,那么这个进程很快就会被系统杀死。因此,一些后台工作不适合脱离四大组件独立运行在后台中,这样进程很快就会被杀死。比较好的办法是将后台工作放入 Service 中从而保证进程有一定的优先级,这样就不会轻易的被系统杀死。

更新

前面说过,在系统配置发生改变后,是可以不用重新创建 Activity 的。如果不想重新创建,可以给 Activity 指定configChanges 属性。比如不想让 Activity 在屏幕旋转的时候重新创建,就可以以下:

1
android:configChanges="orientation"

如果我们想指定多值,可以用 | 分隔。以下列举一些常用的:

类型 含义
keyboardHidden 键盘的可访问性发生了改变,比如用户调出了键盘
screenLayout 屏幕布局发生了改变,很可能是用户激活了另外一个显示设备
fontScale 系统字体缩放比例发生了改变,比如用户选择了一个新字号
uiMode 用户界面模式发生了改变,比如用户开启了夜间模式(API 8 新添加)
orientation 屏幕方向发生了改变,这个是最常用的,比如旋转了手机屏幕
我们一直都向往,面朝大海,春暖花开。 但是几人能做到,心中有爱,四季不败?